﻿using GarminUploader.Connector;
using GarminUploader.Properties;
using log4net;
using log4net.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;

namespace GarminUploader
{
    class Program
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Program));

        private readonly string activitiesFile = "activities.bin";

        // The connectors
        private Dictionary<ConnectorType, BaseConnector> connectors = new Dictionary<ConnectorType, BaseConnector>();

        // The lists of files
        private List<string> fitFiles = new List<string>();
        private ActivityConnectionDict uploadedActivities = new ActivityConnectionDict();
        private FileConnectionDict outstandingFiles = new FileConnectionDict();

        public static void Main(string[] args)
        {
            // Initialise logging
            log4net.Config.XmlConfigurator.Configure();
            System.Net.ServicePointManager.Expect100Continue = false;

            Program program = new Program();
            program.run();
        }

        public void run()
        {
            // Add the connectors
            connectors.Add(ConnectorType.GarminConnect, new GarminConnector());
            connectors.Add(ConnectorType.Strava, new StravaConnector());

            // Load the fit file from the directory from settings
            loadFitFiles();

            // Login to each connector
            login();

            // Load the previously uploaded files
            loadActivitiesFromFile();

            // Get all FIT files from the directory that havent been uploaded
            calculateFilesToUpload();

            //lookupActivities();
            uploadActivities();
            exit();
        }

        private void loadFitFiles()
        {
            // The array to hold all files
            string[] allFiles = new string[] { };

            // Load the directory from settings
            try
            {
                log.Info("Loading directory " + Properties.Settings.Default.directory);
                allFiles = Directory.GetFiles(Properties.Settings.Default.directory);
                log.Info(allFiles.Length + " files found.\r\n");
            }
            catch (Exception exception)
            {
                logErrorAndExit("Unable to load directory.", exception);
            }

            // Loop through the files found and only save the FIT files
            foreach (string filename in allFiles) 
            {
                if (filename.ToLower().EndsWith(".fit"))
                {
                    fitFiles.Add(filename);
                }
            }

            // Exit if no FIT files were found
            if (fitFiles.Count == 0)
            {
                log.Info("No FIT files found in directory.");
                exit();
            }
        }

        private void login()
        {
            // Login to every connector
            foreach (ConnectorType connectorType in connectors.Keys)
            {
                // Get the current connector name
                string connectorName = Enum.GetName(typeof(ConnectorType), connectorType);
                try
                {
                    log.Info("Logging in to " + connectorName);

                    // Get the username and password using reflection
                    // NOTE: The properties file must be named the same as the 
                    // connectorType
                    Type type = Type.GetType("GarminUploader.Properties." + connectorName);
                    var instance = Activator.CreateInstance(type);
                    string username = (string) type.GetProperty("username").GetValue(instance);
                    string password = (string) type.GetProperty("password").GetValue(instance);

                    // Perform the login
                    connectors[connectorType].login(username, password);
                    
                    log.Info("Successfully logged in to " + connectorName + "\r\n");
                }
                catch (Exception exception)
                {
                    logError("Unable to login to " + connectorName + "\r\n", exception);
                }
            }

        }

        private void loadActivitiesFromFile()
        {
            log.Info("Loading previous activities from file.");

            // First attempt to open the file containing the previous uploads
            if (File.Exists(activitiesFile))
            {
                // Open the file
                using (Stream stream = File.OpenRead(activitiesFile))
                {
                    try
                    {
                        // Deserialise into a list of UploadedFiles
                        var bformatter = new BinaryFormatter();
                        uploadedActivities = (ActivityConnectionDict)bformatter.Deserialize(stream);
                        log.Info("Loaded " + uploadedActivities.getActivityCount() + " previous activities.\r\n");
                    }
                    catch (Exception exception)
                    {
                        // If error occurs then set an empty uploaded files
                        logError("Unable to load previous activities from file.\r\n", exception);

                        // Load from online
                        loadActivitiesFromOnline();
                    }
                }
            }
            else
            {
                // If no files have been uploaded previous, initialise to empty list
                log.Info("No previous activities found" + "\r\n");
                loadActivitiesFromOnline();
            }
        }

        private void loadActivitiesFromOnline()
        {
            // Load previous activities from all connectors
            foreach (ConnectorType connectorType in connectors.Keys)
            {
                // Get the current connector name
                string connectorName = Enum.GetName(typeof(ConnectorType), connectorType);

                try
                {
                    log.Info("Loading previous activities from " + connectorName);

                    // Add an entry for every startTime found for this connectorType
                    List<DateTime> startTimes = connectors[connectorType].listActivityStartTimes();
                    foreach (DateTime startTime in startTimes)
                    {
                        uploadedActivities.addConnection(startTime, connectorType);
                    }

                    log.Info("Loaded " + startTimes.Count + " previous activities from " + connectorName + "\r\n");
                }
                catch (Exception exception)
                {
                    // Print error,  but continue
                    logError("Unable to load previous activities from " + connectorName + "\r\n", exception);
                }

                // Save the uploaded files to disk
                saveActivitiesToFile();
            }
        }

        private void calculateFilesToUpload()
        {
            log.Info("Finding activities not yet uploaded.");

            // Loop through all FIT files found
            foreach (string filename in fitFiles)
            {
                // Create a FitFile from the filename
                FitFile fitFile = new FitFile(filename);

                try
                {
                    // Get the start time of the activity
                    DateTime startTime = fitFile.getStartTime();

                    // Loop through the connector types
                    foreach (ConnectorType connectorType in connectors.Keys)
                    {
                        // Check if the file has NOT been uploaded to the current connector
                        if (!uploadedActivities.isActivityConnected(startTime, connectorType))
                        {
                            // Add the connector to the "toBeUploaded"
                            outstandingFiles.addConnection(fitFile, connectorType);
                        }
                    }
                } 
                catch (Exception exception)
                {
                    log.Error("Unable to load FIT file " + filename, exception);
                }
            }

            log.Info("Found " + outstandingFiles.getFiles().Count + " activities to be uploaded.\r\n");
        }

        private void uploadActivities()
        {
            // Loop through the files to upload
            foreach (FitFile file in outstandingFiles.getFiles())
            {
                log.Info("========= Loading " + file + " =========");

                // Loop through the required connectors to upload to
                foreach (ConnectorType connectorType in outstandingFiles.getConnections(file))
                {
                    // Get the current connector name
                    string connectorName = Enum.GetName(typeof(ConnectorType), connectorType);

                    // Upload the file
                    try
                    {
                        log.Info("\r\nUploading file (" + file.getName() + ") to " + connectorName);
                        connectors[connectorType].upload(file);
                        log.Info("New file (" + file.getName() + ") has been uploaded to " + connectorName);

                        // Save to uploadActivities
                        uploadedActivities.addConnection(file.getStartTime(), connectorType);
                    }
                    catch (DuplicateActivityException exception)
                    {
                        // Show error message
                        log.Info("Unable to upload activity (" + file.getName() + ") to " + connectorName + ": " + exception.Message + "\r\n");

                        // Add the file to uploadedActivities since it has been detected as a duplicate
                        uploadedActivities.addConnection(file.getStartTime(), connectorType);
                    }
                    catch (Exception exception)
                    {
                        // Log error, but continue with next file
                        logError("Unable to upload activity (" + file.getName() + ") to " + connectorName +
                            ": " + exception.Message, exception);
                    }
                }

                log.Info("\r\n========= Finished " + file.getName() + " =========\r\n");
            }

            // Save all activities files to file
            saveActivitiesToFile();

            log.Info("Finished processing directory " + Properties.Settings.Default.directory);
        }

        private void saveActivitiesToFile()
        {
            try
            {
                // Serialise the uploadedFiles object to disk
                using (Stream stream = File.Open(activitiesFile, FileMode.Create))
                {
                    new BinaryFormatter().Serialize(stream, uploadedActivities);
                }
            }
            catch (Exception exception)
            {
                logError("Unable to save activities to file.", exception);
            }
        }

        private void logError(String message, Exception exception)
        {
            // Print to console
            log.Info(message);
            log.Info("See 'error.log' for more details.");

            // Print error to file
            log.Error(exception.Message, exception);
        }

        private void exit() {
            // Wait for user to confirm
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
            Environment.Exit(0);
        }

        private void logErrorAndExit(String message, Exception exception)
        {
            logError(message, exception);
            exit();
        }
    }
}
